//=============================================================================
//
//   Copyright (C) 2020 All rights reserved.
//
//   Filename:		model.rs
//
//   Author: Wang Zhecheng - wangzhecheng@yeah.net
//
//   Date: 2020-10-09 21:19
//
//   Last Editors: Wang Zhecheng - wangzhecheng@yeah.net
//
//   Last modified:	2020-11-06 18:27
//
//   Description:
//
//=============================================================================
use crate::component::{
    mesh_component::MeshComponentLike,
    time_sequence::{time_phase::TimePhase, StepLike},
};
use crate::dimension::DimType;
use crate::geometry::{mesh::Mesh, point::Point};
use crate::io::{
    data_parser::{DataParser, JsonParser},
    INDENT,
};
use crate::material::{build_material, MaterialLike};
use crate::method::{
    build_method,
    unknowns::{build_unknown, Unknown},
    NumericalMethod,
};
use crate::Real;
use crate::util::{indexable::Indexable, observer_interface::ObserverInterface, HasName};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::{Rc, Weak};

pub struct Model<DT: DimType> {
    time: Real,
    iter: usize,
    mesh: Mesh<DT>,
    pt_list: HashMap<usize, Rc<Point<DT::Dim>>>,
    mat_list: HashMap<usize, Rc<dyn MaterialLike<DT>>>,
    shp_list: [HashMap<String, Rc<RefCell<dyn ElementLike<DT>>>>; 4],
    phase_list: Vec<TimePhase>,
    parser: JsonParser,
    phantom_point_num: usize,
    observer_list: Vec<Box<dyn ObserverInterface>>,
    method: NumericalMethod<DT>,
    coupler: Vec<Unknown<DT>>,
}

impl<DT: DimType> Default for Model<DT> {
    #[inline]
    fn default() -> Self {
        Self {
            time: 0 as Real,
            iter: 0usize,
            mesh: Mesh::new(),
            pt_list: HashMap::new(),
            mat_list: HashMap::new(),
            shp_list: [
                HashMap::new(),
                HashMap::new(),
                HashMap::new(),
                HashMap::new(),
            ],
            phase_list: Vec::new(),
            parser: JsonParser::default(),
            phantom_point_num: 0usize,
            observer_list: Vec::new(),
            method: NumericalMethod::None,
            coupler: Vec::new(),
        }
    }
}

impl<DT: DimType> Model<DT> {
    // point related ------------------------------------
    /// store a new point, return the reference of inserted point
    #[inline]
    pub fn append_point(&mut self, rhs: Point<DT::Dim>) -> Weak<Point<DT::Dim>> {
        let mut new_point = rhs.clone();
        let id = match new_point.get_index(){
            None =>{
                self.phantom_point_num += 1;
                let new_id =                 usize::MAX - self.phantom_point_num ;
                new_point.set_index(new_id);
                new_id
            },
            Some(i)=>i,
        }
        self.pt_list.insert(id, Rc::new(rhs));
        self.get_point(id)
    }
    /// reach a point in current model by index number
    #[inline]
    pub fn get_point(&self, id: usize) -> Weak<Point<DT::Dim>> {
        match self.pt_list.get(&id) {
            None => panic!("point #{} doesn't exist in current model."),
            Some(p) => Rc::downgrade(p),
        }
    }
    // shape related ------------------------------------
    pub fn append_shape(
        &mut self,
        rhs: Rc<RefCell<dyn ElementLike<DT>>>,
    ) -> Weak<RefCell<dyn ElementLike<DT>>> {
        let dim = rhs.borrow().get_topo_dim();
        let key = rhs.borrow().get_hashkey();
        self.shp_list
            .get_mut(dim)
            .expect("Failed to access shape list in model")
            .entry(key.clone())
            .or_insert(rhs);
        Rc::downgrade(
            self.shp_list
                .get(dim)
                .expect("Failed to access shape list in model")
                .get(&key)
                .expect("Failed to downgrade shape stored in model to weak point"),
        )
    }

    // data parser related ------------------------------------
    /// set data parser
    #[inline]
    pub fn read(&mut self, raw_file_name: &str) {
        self.parser = DataParser::from_path(raw_file_name);
    }
    /// reach the data parser
    #[inline]
    fn get_parser(&self) -> &JsonParser {
        &self.parser
    }
    #[inline]
    pub fn set_parser(&mut self, rhs: JsonParser) {
        self.parser = rhs
    }

    // observer related------------------------------------
    /// notify all observers
    #[inline]
    fn notify(&mut self) {
        for obs in &mut self.observer_list {
            obs.update();
        }
    }
    /// append a new observer
    #[inline]
    pub fn append_observer(&mut self, obs: Box<dyn ObserverInterface>) {
        self.observer_list.push(obs);
    }

    /// brief build this Problem
    #[inline]
    pub fn build_problem(&mut self) {
        log::trace!("Start building problem...");
        // 0. register everything
        // self.register_type();
        // 1. set up constances
        self.setup_params();
        // 2. build material list
        self.build_material();
        // 3. build Phases
        self.build_time_sequence();
        // 4. create mesh object and populate mesh by xml resource data
        self.build_mesh();
        // 5. create cracks
        self.build_init_cracks();
        // 6. output/plot initial mesh
        self.notify();
    }

    ///
    /// configure problem
    ///
    fn setup_params(&mut self) {
        log::trace!("Configurate problem...");
        let root = self.parser.get_configuration();

        // -------------------------------
        let method_name = root
            .get("method")
            .expect("There is no \"method\" node in configuration node.");
        self.method = build_method(
            method_name
                .as_str()
                .expect("Failed to convert method name to str"),
        );

        // -------------------------------
        let coupler_name = root
            .get("unknown")
            .expect("There is no \"unknown\" node in configuration node.");
        for name in coupler_name.as_array().unwrap() {
            build_unknown::<DT>(name.as_str().expect("Failed to convert method name to str"));
        }
    }

    ///
    /// build materials
    ///
    #[inline]
    fn build_material(&mut self) {
        log::trace!("Building materials...");
        self.mat_list.clear();
        for mat in self.parser.get_material_data().as_array().unwrap() {
            let id = mat
                .get("id")
                .expect("There is no \"id\" in material json data")
                .as_u64()
                .expect("Failed to convert serde_json value to u64") as usize;
            self.mat_list.insert(id, build_material::<DT>(mat));
        }
    }
    #[inline]
    fn get_material(&self, index: usize) -> Weak<dyn MaterialLike<DT>> {
        match self.mat_list.get(&index) {
            None => panic!("Material #{} is not found", index),
            Some(m) => Rc::downgrade(m),
        }
    }

    ///
    /// build time sequence
    ///
    #[inline]
    fn build_time_sequence(&mut self) {
        log::trace!("Building time sequence...");
        self.phase_list.clear();
        self.time = 0 as Real;
        for data in self.parser.get_phase_data().as_array().unwrap() {
            let phase: TimePhase = serde_json::from_value(data.clone())
                .expect("Failed to deserialize TimePhase from json data");
            self.phase_list.push(phase);
        }
    }

    #[inline]
    fn build_init_cracks(&mut self) {}

    ///
    /// solve problem
    ///
    #[inline]
    pub fn solve_problem(&self) {}

    ///
    /// build mesh
    ///
    #[inline]
    fn build_mesh(&mut self) {
        log::trace!("Building main mesh...");

        // self.method
        //     .build_mesh(self.parser.get_topology_data(), self);
    }
    #[inline]
    pub fn get_mesh(&self) -> &Mesh<DT> {
        &self.mesh
    }
    #[inline]
    pub fn get_mesh_mut(&mut self) -> &mut Mesh<DT> {
        &mut self.mesh
    }
}

impl<DT: DimType> std::fmt::Display for Model<DT> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{} Model. Project name: {}\n",
            self.method.type_name(),
            self.parser
                .get_proj_name()
                .to_str()
                .expect("Failed to convert project name to str")
        )?;
        write!(f, "{}Total time: {}\n", INDENT, self.time)?;
        write!(f, "{}Material number: {}\n", INDENT, self.mat_list.len())?;
        for mat in self.mat_list.values() {
            write!(
                f,
                "{}{}Material #{} type {}\n",
                INDENT,
                INDENT,
                mat.get_index().expect("This Material Type hasn't been indexed!"),
                mat.type_name()
            )?;
        }
        write!(f, "{}Geometries:\n", INDENT)?;
        write!(
            f,
            "{}{}Point number: {}\n",
            INDENT,
            INDENT,
            self.pt_list.len()
        )?;
        write!(
            f,
            "{}{}Vertex number: {}\n",
            INDENT,
            INDENT,
            self.shp_list
                .get(0)
                .expect("Failed to get vertex list in model")
                .len()
        )?;
        write!(
            f,
            "{}{}Segment number: {}\n",
            INDENT,
            INDENT,
            self.shp_list
                .get(1)
                .expect("Failed to get segment list in model")
                .len()
        )?;
        write!(
            f,
            "{}{}Planar number: {}\n",
            INDENT,
            INDENT,
            self.shp_list
                .get(2)
                .expect("Failed to get planar list in model")
                .len()
        )?;
        write!(
            f,
            "{}{}Cubic number: {}\n",
            INDENT,
            INDENT,
            self.shp_list
                .get(3)
                .expect("Failed to get cubic list in model")
                .len()
        )?;
        write!(f, "{}Phase number: {}\n", INDENT, self.phase_list.len())?;
        for p in &self.phase_list {
            write!(
                f,
                "{}{}Time phase #{} from {} to {}",
                INDENT,
                INDENT,
                p.get_index().expect("This time phase hasn't been indexed"),
                p.get_stime(),
                p.get_etime()
            )?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::dimension::PlaneStrain;
    use crate::io::{
        data_parser::{DataParser, JsonParser},
        vtu_writer::VTUWriter,
    };
    use std::env;

    #[test]
    fn build_model() {
        env::set_var("RUST_APP_LOG", "trace");
        pretty_env_logger::init_custom_env("RUST_APP_LOG");

        let parser = JsonParser::from_str(simple_input().as_str());
        let writer = VTUWriter::from_path(parser.get_path());

        let mut model = Model::<PlaneStrain>::default();
        model.set_parser(parser);
        model.append_observer(Box::new(writer));
        model.build_problem();

        let mat1 = model.get_material(0).upgrade().unwrap();
        println!("{}", mat1.get_elastic_matrix());
        println!("{}", model);
    }

    fn simple_input() -> String {
        r#"
        {
            "configuration": {
                "name": "P52",
                "method": "FEM",
                "unknown": ["Displace", "Pore"],
                "displace_pattern": ["Standard", "Signed", "Branch"],
                "equation": "Static",
                "solver": "Implicit",
                "load": "Incremental",
                "plane_stress": "false"
            },
            "io": {
                "log": { "log_level": "info", "detail_file_name": "" },
                "breakpoint": "",
                "output": { "path": "" }
            },
            "calculation": {
                "paralleled": "true",
                "time_step": { "theta": 0.8, "large_defomation": "false" },
                "iteration": {
                    "max_time": 100,
                    "nonlinear_scheme": "initial_stress",
                    "tolerance": { "relative": 0.01, "absolute": 0 }
                },
                "xfem": {
                    "active": "true",
                    "branch_enrich_radius": 0,
                    "crack_segment": { "default_delta_length": 0.03, "gauss_num": 4 },
                    "sif_integral": {
                        "domain": { "shape": "circle", "radius": 9 },
                        "qfunction": { "exponent": 1 }
                    }
                },
                "gravity_scheme": "k0",
                "water_density": { "value": 10, "unit": "kN/m^3" },
                "atm": { "value": 100, "unit": "kPa" }
            },
            "topology": {
                "shape_function_degree": 1,
                "elem_nodes_order": "clockwise",
                "elem_gauss_num": [
                    { "name": "Segment", "value": 3 },
                    { "name": "Triangle", "value": 3 },
                    { "name": "Quadrangle", "value": 4 },
                    { "name": "Tetrahedral", "value": 4 },
                    { "name": "Tri_prism", "value": 6 },
                    { "name": "Octahedral", "value": 8 }
                ],
                "cracks": [],
                "nodes": [
                    { "id": 11, "coord": [0, 0, 0] },
                    { "id": 6, "coord": [0, -2, 0] },
                    { "id": 7, "coord": [2, 0, 0] },
                    { "id": 8, "coord": [0, 2, 0] },
                    { "id": 9, "coord": [-2, 0, 0] },
                    { "id": 0, "coord": [-2, -2, 0] },
                    { "id": 1, "coord": [2, -2, 0] },
                    { "id": 2, "coord": [2, 2, 0] },
                    { "id": 3, "coord": [-2, 2, 0] },
                    { "id": 10, "coord": [0, 4, 0] },
                    { "id": 4, "coord": [-2, 4, 0] },
                    { "id": 5, "coord": [2, 4, 0] }
                ],
                "elements": [
                    { "id": 0, "type": "quadrangle", "mat": 1, "nodes": [0, 6, 11, 9] },
                    { "id": 1, "type": "quadrangle", "mat": 1, "nodes": [9, 11, 8, 3] },
                    { "id": 2, "type": "quadrangle", "mat": 1, "nodes": [6, 1, 7, 11] },
                    { "id": 3, "type": "quadrangle", "mat": 1, "nodes": [11, 7, 2, 8] },
                    { "id": 4, "type": "quadrangle", "mat": 2, "nodes": [2, 5, 10, 8] },
                    { "id": 5, "type": "quadrangle", "mat": 2, "nodes": [8, 10, 4, 3] }
                ]
            },
            "materials": [
                {
                    "id": 1,
                    "type": "DuncanChang",
                    "volume_weight": 19,
                    "friction_angle": 30,
                    "dilation_angle": 30,
                    "cohesion": 10,
                    "permeability": [1e-5, 1e-5, 1e-5],
                    "zeta": 0.1,
                    "K_IC": 2.9,
                    "elastic_modulus": 1000000.0,
                    "elastic_poisson": 0.3,
                    "Rf": 201,
                    "K": 202,
                    "n": 203,
                    "G": 204,
                    "F": 205,
                    "D": 206
                },
                {
                    "id": 2,
                    "type": "elastic",
                    "volume_weight": 19,
                    "friction_angle": 30,
                    "dilation_angle": 30,
                    "cohesion": 10,
                    "permeability": [1e-5, 1e-5, 1e-5],
                    "zeta": 0.1,
                    "K_IC": 2.9,
                    "elastic_modulus": 1000000.0,
                    "elastic_poisson": 0.3
                }
            ],
            "init_condition": { "init_displacement": [], "init_pore": [] },
            "phases": [
                {
                    "id": 1,
                    "start_time": 0,
                    "end_time": 10,
                    "delta_time": 10,
                    "element_num": 9,
                    "boundary_condition": {
                        "dirichlet": [
                            { "node": 6, "dof": "x0", "type": "Linear", "start": 0.0, "end": 1.0 },
                            { "node": 0, "dof": "x0", "type": "Linear", "start": 0.0, "end": 1.0 },
                            { "node": 1, "dof": "x0", "type": "Linear", "start": 0.0, "end": 1.0 }
                        ],
                        "neumann": []
                    }
                }
            ]
        }
        "#
        .to_string()
    }
}
